/**********************************************************************
 *
 * Name:     mitab_spatialref.cpp
 * Project:  MapInfo TAB Read/Write library
 * Language: C++
 * Purpose:  Implementation of the SpatialRef stuff in the TABFile class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 **********************************************************************
 * Copyright (c) 1999-2001, Frank Warmerdam
 * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "cpl_port.h"
#include "mitab.h"

#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "mitab_priv.h"
#include "ogr_spatialref.h"
#include "ogr_srs_api.h"

CPL_CVSID("$Id$")

/* -------------------------------------------------------------------- */
/*      This table was automatically generated by doing translations    */
/*      between mif and tab for each datum, and extracting the          */
/*      parameters from the tab file.  The EPSG codes and OGC names     */
/*      were added afterwards and may be incomplete or inaccurate.       */
/* -------------------------------------------------------------------- */

extern const MapInfoDatumInfo asDatumInfoList[];
extern const MapInfoSpheroidInfo asSpheroidInfoList[];

/* EPSG code, MapInfo datum ID (or 9999), OGC Name, datum parameters... */
const MapInfoDatumInfo asDatumInfoList[] =
{

{ 0,    104, "WGS_1984",                   28,0, 0, 0, 0, 0, 0, 0, 0},
{ 6269, 74,  "North_American_Datum_1983",  0, 0, 0, 0, 0, 0, 0, 0, 0},

{ 0,    0,  "",                            29, 0,   0,    0,   0, 0, 0, 0, 0}, // Datum ignore

{ 6201, 1,  "Adindan",                     6, -162, -12,  206, 0, 0, 0, 0, 0},
{ 6205, 2,  "Afgooye",                     3, -43,  -163, 45,  0, 0, 0, 0, 0},
{ 6204, 3,  "Ain_el_Abd_1970",             4, -150, -251, -2,  0, 0, 0, 0, 0},
{ 0,    4,  "Anna_1_Astro_1965",           2, -491, -22,  435, 0, 0, 0, 0, 0},
{ 6209, 5,  "Arc_1950",                    15,-143, -90,  -294,0, 0, 0, 0, 0},
{ 6210, 6,  "Arc_1960",                    6, -160, -8,   -300,0, 0, 0, 0, 0},
{ 0,    7,  "Ascension_Islands",           4, -207, 107,  52,  0, 0, 0, 0, 0},
{ 0,    8,  "Astro_Beacon_E",              4, 145,  75,   -272,0, 0, 0, 0, 0},
{ 0,    9,  "Astro_B4_Sorol_Atoll",        4, 114,  -116, -333,0, 0, 0, 0, 0},
{ 0,    10, "Astro_Dos_71_4",              4, -320, 550,  -494,0, 0, 0, 0, 0},
{ 0,    11, "Astronomic_Station_1952",     4, 124,  -234, -25, 0, 0, 0, 0, 0},
{ 6202, 12, "Australian_Geodetic_Datum_66",2, -133, -48,  148, 0, 0, 0, 0, 0},
{ 6203, 13, "Australian_Geodetic_Datum_84",2, -134, -48,  149, 0, 0, 0, 0, 0},
{ 0,    14, "Bellevue_Ign",                4, -127, -769, 472, 0, 0, 0, 0, 0},
{ 6216, 15, "Bermuda_1957",                7, -73,  213,  296, 0, 0, 0, 0, 0},
{ 6218, 16, "Bogota",                      4, 307,  304,  -318,0, 0, 0, 0, 0},
{ 6221, 17, "Campo_Inchauspe",             4, -148, 136,  90,  0, 0, 0, 0, 0},
{ 0,    18, "Canton_Astro_1966",           4, 298,  -304, -375,0, 0, 0, 0, 0},
{ 6222, 19, "Cape",                        6, -136, -108, -292,0, 0, 0, 0, 0},
{ 6717, 20, "Cape_Canaveral",              7, -2,   150,  181, 0, 0, 0, 0, 0},
{ 6223, 21, "Carthage",                    6, -263, 6,    431, 0, 0, 0, 0, 0},
{ 6672, 22, "Chatham_1971",                4, 175,  -38,  113, 0, 0, 0, 0, 0},
{ 6224, 23, "Chua",                        4, -134, 229,  -29, 0, 0, 0, 0, 0},
{ 6225, 24, "Corrego_Alegre",              4, -206, 172,  -6,  0, 0, 0, 0, 0},
{ 6211, 25, "Batavia",                     10,-377,681,   -50, 0, 0, 0, 0, 0},
{ 0,    26, "Dos_1968",                    4, 230,  -199, -752,0, 0, 0, 0, 0},
{ 6719, 27, "Easter_Island_1967",          4, 211,  147,  111, 0, 0, 0, 0, 0},
{ 6230, 28, "European_Datum_1950",         4, -87,  -98,  -121,0, 0, 0, 0, 0},
{ 6668, 29, "European_Datum_1979",         4, -86,  -98,  -119,0, 0, 0, 0, 0},
{ 6233, 30, "Gandajika_1970",              4, -133, -321, 50,  0, 0, 0, 0, 0},
{ 6272, 31, "New_Zealand_GD49",            4, 84,   -22,  209, 0, 0, 0, 0, 0},
{ 6272, 31, "New_Zealand_Geodetic_Datum_1949",4,84, -22,  209, 0, 0, 0, 0, 0},
{ 0,    32, "GRS_67",                      21,0,    0,    0,   0, 0, 0, 0, 0},
{ 0,    33, "GRS_80",                      0, 0,    0,    0,   0, 0, 0, 0, 0},
{ 6171, 33, "Reseau_Geodesique_Francais_1993",0, 0, 0,    0,   0, 0, 0, 0, 0},
{ 6619, 33, "SWEREF99",                    0, 0,    0,    0,   0, 0, 0, 0, 0},
{ 6675, 34, "Guam_1963",                   7, -100, -248, 259, 0, 0, 0, 0, 0},
{ 0,    35, "Gux_1_Astro",                 4, 252,  -209, -751,0, 0, 0, 0, 0},
{ 6254, 36, "Hito_XVIII_1963",             4, 16,   196,  93,  0, 0, 0, 0, 0},
{ 6658, 37, "Hjorsey_1955",                4, -73,  46,   -86, 0, 0, 0, 0, 0},
{ 6738, 38, "Hong_Kong_1963",              4, -156, -271, -189,0, 0, 0, 0, 0},
{ 6236, 39, "Hu_Tzu_Shan",                 4, -634, -549, -201,0, 0, 0, 0, 0},
{ 0,    40, "Indian_Thailand_Vietnam",     11,214,  836,  303, 0, 0, 0, 0, 0},
{ 0,    41, "Indian_Bangladesh",           11,289,  734,  257, 0, 0, 0, 0, 0},
{ 6299, 42, "Ireland_1965",                13,506,  -122, 611, 0, 0, 0, 0, 0},
{ 0,    43, "ISTS_073_Astro_1969",         4, 208,  -435, -229,0, 0, 0, 0, 0},
{ 6725, 44, "Johnston_Island_1961",        4, 191,  -77,  -204,0, 0, 0, 0, 0},
{ 6244, 45, "Kandawala",                   11,-97,  787,  86,  0, 0, 0, 0, 0},
{ 0,    46, "Kerguyelen_Island",           4, 145,  -187, 103, 0, 0, 0, 0, 0},
{ 6245, 47, "Kertau",                      17,-11,  851,  5,   0, 0, 0, 0, 0},
{ 0,    48, "L_C_5_Astro",                 7, 42,   124,  147, 0, 0, 0, 0, 0},
{ 6251, 49, "Liberia_1964",                6, -90,  40,   88,  0, 0, 0, 0, 0},
{ 0,    50, "Luzon_Phillippines",          7, -133, -77,  -51, 0, 0, 0, 0, 0},
{ 0,    51, "Luzon_Mindanao_Island",       7, -133, -79,  -72, 0, 0, 0, 0, 0},
{ 6256, 52, "Mahe_1971",                   6, 41,   -220, -134,0, 0, 0, 0, 0},
{ 0,    53, "Marco_Astro",                 4, -289, -124, 60,  0, 0, 0, 0, 0},
{ 6262, 54, "Massawa",                     10,639,  405,  60,  0, 0, 0, 0, 0},
{ 6261, 55, "Merchich",                    16,31,   146,  47,  0, 0, 0, 0, 0},
{ 0,    56, "Midway_Astro_1961",           4, 912,  -58,  1227,0, 0, 0, 0, 0},
{ 6263, 57, "Minna",                       6, -92,  -93,  122, 0, 0, 0, 0, 0},
{ 0,    58, "Nahrwan_Masirah_Island",      6, -247, -148, 369, 0, 0, 0, 0, 0},
{ 0,    59, "Nahrwan_Un_Arab_Emirates",    6, -249, -156, 381, 0, 0, 0, 0, 0},
{ 0,    60, "Nahrwan_Saudi_Arabia",        6, -231, -196, 482, 0, 0, 0, 0, 0},
{ 6271, 61, "Naparima_1972",               4, -2,   374,  172, 0, 0, 0, 0, 0},
{ 6267, 62, "NAD_1927",                    7, -8,   160,  176, 0, 0, 0, 0, 0},
{ 6267, 62, "North_American_Datum_1927",   7, -8,   160,  176, 0, 0, 0, 0, 0},
{ 0,    63, "NAD_27_Alaska",               7, -5,   135,  172, 0, 0, 0, 0, 0},
{ 0,    64, "NAD_27_Bahamas",              7, -4,   154,  178, 0, 0, 0, 0, 0},
{ 0,    65, "NAD_27_San_Salvador",         7, 1,    140,  165, 0, 0, 0, 0, 0},
{ 0,    66, "NAD_27_Canada",               7, -10,  158,  187, 0, 0, 0, 0, 0},
{ 0,    67, "NAD_27_Canal_Zone",           7, 0,    125,  201, 0, 0, 0, 0, 0},
{ 0,    68, "NAD_27_Caribbean",            7, -7,   152,  178, 0, 0, 0, 0, 0},
{ 0,    69, "NAD_27_Central_America",      7, 0,    125,  194, 0, 0, 0, 0, 0},
{ 0,    70, "NAD_27_Cuba",                 7, -9,   152,  178, 0, 0, 0, 0, 0},
{ 0,    71, "NAD_27_Greenland",            7, 11,   114,  195, 0, 0, 0, 0, 0},
{ 0,    72, "NAD_27_Mexico",               7, -12,  130,  190, 0, 0, 0, 0, 0},
{ 0,    73, "NAD_27_Michigan",             8, -8,   160,  176, 0, 0, 0, 0, 0},
{ 0,    75, "Observatorio_1966",           4, -425, -169, 81,  0, 0, 0, 0, 0},
{ 0,    76, "Old_Egyptian",                22,-130, 110, -13,  0, 0, 0, 0, 0},
{ 6135, 77, "Old_Hawaiian",                7, 61,   -285, -181,0, 0, 0, 0, 0},
{ 0,    78, "Oman",                        6, -346, -1,   224, 0, 0, 0, 0, 0},
{ 6277, 79, "OSGB_1936",                   9, 375,  -111, 431, 0, 0, 0, 0, 0},
{ 0,    80, "Pico_De_Las_Nieves",          4, -307, -92,  127, 0, 0, 0, 0, 0},
{ 6729, 81, "Pitcairn_Astro_1967",         4, 185,  165,  42,  0, 0, 0, 0, 0},
{ 6248, 82, "Provisional_South_American",  4, -288, 175,  -376,0, 0, 0, 0, 0},
{ 6139, 83, "Puerto_Rico",                 7, 11,   72,   -101,0, 0, 0, 0, 0},
{ 6614, 84, "Qatar_National",              4, -128, -283, 22,  0, 0, 0, 0, 0},
{ 6287, 85, "Qornoq",                      4, 164,  138, -189, 0, 0, 0, 0, 0},
{ 6627, 86, "Reunion",                     4, 94,   -948,-1262,0, 0, 0, 0, 0},
{ 6265, 87, "Monte_Mario",                 4, -225, -65, 9,    0, 0, 0, 0, 0},
{ 0,    88, "Santo_Dos",                   4, 170,  42,  84,   0, 0, 0, 0, 0},
{ 0,    89, "Sao_Braz",                    4, -203, 141, 53,   0, 0, 0, 0, 0},
{ 6292, 90, "Sapper_Hill_1943",            4, -355, 16,  74,   0, 0, 0, 0, 0},
{ 6293, 91, "Schwarzeck",                  14,616,  97,  -251, 0, 0, 0, 0, 0},
{ 6618, 92, "South_American_Datum_1969",   24,-57,  1,   -41,  0, 0, 0, 0, 0},
{ 0,    93, "South_Asia",                  19,7,    -10, -26,  0, 0, 0, 0, 0},
{ 0,    94, "Southeast_Base",              4, -499, -249,314,  0, 0, 0, 0, 0},
{ 0,    95, "Southwest_Base",              4, -104, 167, -38,  0, 0, 0, 0, 0},
{ 6298, 96, "Timbalai_1948",               11,-689, 691, -46,  0, 0, 0, 0, 0},
{ 6301, 97, "Tokyo",                       10,-128, 481, 664,  0, 0, 0, 0, 0},
{ 0,    98, "Tristan_Astro_1968",          4, -632, 438, -609, 0, 0, 0, 0, 0},
{ 6731, 99, "Viti_Levu_1916",              6, 51,   391, -36,  0, 0, 0, 0, 0},
{ 0,    100, "Wake_Entiwetok_1960",        23,101,  52,  -39,  0, 0, 0, 0, 0},
{ 0,    101, "WGS_60",                     26,0,    0,   0,    0, 0, 0, 0, 0},
{ 6760, 102, "WGS_66",                     27,0,    0,   0,    0, 0, 0, 0, 0},
{ 6322, 103, "WGS_1972",                   1, 0,    8,   10,   0, 0, 0, 0, 0},
{ 6322, 103, "World_Geodetic_System_1972", 1, 0,    8,   10,   0, 0, 0, 0, 0},
{ 6326, 104, "WGS_1984",                   28,0,    0,   0,    0, 0, 0, 0, 0},
{ 6309, 105, "Yacare",                     4, -155, 171, 37,   0, 0, 0, 0, 0},
{ 6311, 106, "Zanderij",                   4, -265, 120, -358, 0, 0, 0, 0, 0},
{ 0,    107, "NTF",                        30,-168, -60, 320,  0, 0, 0, 0, 0},
{ 6231, 108, "European_Datum_1987",        4, -83,  -96, -113, 0, 0, 0, 0, 0},
{ 0,    109, "Netherlands_Bessel",         10,593,  26,  478,  0, 0, 0, 0, 0},
{ 0,    110, "Belgium_Hayford",            4, 81,   120, 129,  0, 0, 0, 0, 0},
{ 0,    111, "NWGL_10",                    1, -1,   15,  1,    0, 0, 0, 0, 0},
{ 6124, 112, "Rikets_koordinatsystem_1990",10,498,  -36, 568,  0, 0, 0, 0, 0},
{ 0,    113, "Lisboa_DLX",                 4, -303, -62, 105,  0, 0, 0, 0, 0},
{ 0,    114, "Melrica_1973_D73",           4, -223, 110, 37,   0, 0, 0, 0, 0},
{ 6258, 115, "Euref_89",                   0, 0,    0,   0,    0, 0, 0, 0, 0},
{ 6283, 116, "GDA94",                      0, 0,    0,   0,    0, 0, 0, 0, 0},
{ 6283, 116, "Geocentric_Datum_of_Australia_1994", 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 6167, 117, "NZGD2000",                   0, 0,    0,   0,    0, 0, 0, 0, 0},
{ 6167, 117, "New_Zealand_Geodetic_Datum_2000",0,0, 0,   0,    0, 0, 0, 0, 0},
{ 6169, 118, "America_Samoa",              7, -115, 118, 426,  0, 0, 0, 0, 0},
{ 0,    119, "Antigua_Astro_1965",         6, -270, 13,  62,   0, 0, 0, 0, 0},
{ 6713, 120, "Ayabelle_Lighthouse",        6, -79, -129, 145,  0, 0, 0, 0, 0},
{ 6219, 121, "Bukit_Rimpah",               10,-384, 664, -48,  0, 0, 0, 0, 0},
{ 0,    122, "Estonia_1937",               10,374, 150,  588,  0, 0, 0, 0, 0},
{ 6155, 123, "Dabola",                     6, -83, 37,   124,  0, 0, 0, 0, 0},
{ 6736, 124, "Deception_Island",           6, 260, 12,   -147, 0, 0, 0, 0, 0},
{ 0,    125, "Fort_Thomas_1955",           6, -7, 215,   225,  0, 0, 0, 0, 0},
{ 0,    126, "Graciosa_base_1948",         4, -104, 167, -38,  0, 0, 0, 0, 0},
{ 6255, 127, "Herat_North",                4, -333, -222,114,  0, 0, 0, 0, 0},
{ 0,    128, "Hermanns_Kogel",             10,682, -203, 480,  0, 0, 0, 0, 0},
{ 6240, 129, "Indian",                     50,283, 682,  231,  0, 0, 0, 0, 0},
{ 6239, 130, "Indian_1954",                11,217, 823,  299,  0, 0, 0, 0, 0},
{ 6131, 131, "Indian_1960",                11,198, 881,  317,  0, 0, 0, 0, 0},
{ 6240, 132, "Indian_1975",                11,210, 814,  289,  0, 0, 0, 0, 0},
{ 6238, 133, "Indonesian_Datum_1974",      4, -24, -15,  5,    0, 0, 0, 0, 0},
{ 0,    134, "ISTS061_Astro_1968",         4, -794, 119, -298, 0, 0, 0, 0, 0},
{ 0,    135, "Kusaie_Astro_1951",          4, 647, 1777, -1124,0, 0, 0, 0, 0},
{ 6250, 136, "Leigon",                     6, -130, 29,  364,  0, 0, 0, 0, 0},
{ 0,    137, "Montserrat_Astro_1958",      6, 174, 359,  365,  0, 0, 0, 0, 0},
{ 6266, 138, "Mporaloko",                  6, -74, -130, 42,   0, 0, 0, 0, 0},
{ 0,    139, "North_Sahara_1959",          6, -186, -93, 310,  0, 0, 0, 0, 0},
{ 0,    140, "Observatorio_Met_1939",      4, -425, -169,81,   0, 0, 0, 0, 0},
{ 6620, 141, "Point_58",                   6, -106, -129,165,  0, 0, 0, 0, 0},
{ 6282, 142, "Pointe_Noire",               6, -148, 51,  -291, 0, 0, 0, 0, 0},
{ 6615, 143, "Porto_Santo_1936",           4, -499, -249,314,  0, 0, 0, 0, 0},
{ 6616, 144, "Selvagem_Grande_1938",       4, -289, -124,60,   0, 0, 0, 0, 0},
{ 0,    145, "Sierra_Leone_1960",          6, -88,  4,   101,  0, 0, 0, 0, 0},
{ 6156, 146, "S_JTSK_Ferro",               10, 589, 76,  480,  0, 0, 0, 0, 0},
{ 6297, 147, "Tananarive_1925",            4, -189, -242,-91,  0, 0, 0, 0, 0},
{ 6811, 148, "Voirol_1874",                6, -73,  -247,227,  0, 0, 0, 0, 0},
{ 0,    149, "Virol_1960",                 6, -123, -206,219,  0, 0, 0, 0, 0},
{ 6148, 150, "Hartebeesthoek94",           28,   0,    0,  0,  0, 0, 0, 0, 0},
{ 6122, 151, "ATS77",                      51,   0,    0,  0,  0, 0, 0, 0, 0},
{ 6612, 152, "JGD2000",                    0,    0,    0,  0,  0, 0, 0, 0, 0},
{ 0,    153, "HGRS87",                     0, -199.87, 74.79, 246.62, 0, 0, 0, 0, 0},
{ 6214, 154, "Beijing 1954",               3, -31.4, 144.3, 81.2, 0, 0, 0, 0, 0},
{ 6754, 155, "Libya (LGD 2006)",           4, 208.4058, 109.8777, 2.5764, 0, 0, 0, 0, 0},
{ 6317, 156, "Dealul Piscului 1970",       3, 28, -121, -77, 0, 0, 0, 0, 0},
{ 0,    157, "WGS_1984",                   54, 0, 0, 0, 0, 0, 0, 0, 0}, // Google merc
{ 6150, 158, "CH1903+ datum for Switzerland", 10, 674.374, 15.056, 405.346, 0, 0, 0, 0, 0},
{ 0,    159, "Schwarzeck (updated) datum for Namibia", 14, 616.8, 103.3, -256.9, 0, 0, 0, 0, 0 },
{ 0,    161, "NOAA GCS_Sphere",            55, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0,    1000,"DHDN_Potsdam_Rauenberg",     10,582,  105, 414, -1.04, -0.35, 3.08, 8.3, 0},
{ 6284, 1001,"Pulkovo_1942",               3, 24,   -123, -94, -0.02, 0.25, 0.13, 1.1, 0},
{ 6807, 1002,"NTF_Paris_Meridian",         30,-168, -60, 320, 0, 0, 0, 0, 2.337229166667},
{ 6149, 1003,"Switzerland_CH_1903",        10,660.077,13.551, 369.344, 0.804816, 0.577692, 0.952236, 5.66,0},
{ 6237, 1004,"Hungarian_Datum_1972",       21,-56,  75.77, 15.31, -0.37, -0.2, -0.21, -1.01, 0},
{ 0,    1005,"Cape_7_Parameter",           28,-134.73,-110.92, -292.66, 0, 0, 0, 1, 0},
{ 6203, 1006,"AGD84_7_Param_Aust",         2, -117.763,-51.51, 139.061, -0.292, -0.443, -0.277, -0.191, 0},
{ 0,    1007,"AGD66_7_Param_ACT",          2, -129.193,-41.212, 130.73, -0.246, -0.374, -0.329, -2.955, 0},
{ 0,    1008,"AGD66_7_Param_TAS",          2, -120.271,-64.543, 161.632, -0.2175, 0.0672, 0.1291, 2.4985, 0},
{ 0,    1009,"AGD66_7_Param_VIC_NSW",      2, -119.353,-48.301, 139.484, -0.415, -0.26, -0.437, -0.613, 0},
{ 6272, 1010,"NZGD_7_Param_49",            4, 59.47, -5.04, 187.44, -0.47, 0.1, -1.024, -4.5993, 0},
{ 0,    1011,"Rikets_Tri_7_Param_1990",    10,419.3836, 99.3335, 591.3451, -0.850389, -1.817277, 7.862238, -0.99496, 0},
{ 0,    1012,"Russia_PZ90",                52, -1.08,-0.27,-0.9,0, 0, -0.16,-0.12, 0},
{ 0,    1013,"Russia_SK42",                52, 23.92,-141.27,-80.9, 0, -0.35,-0.82, -0.12, 0},
{ 0,    1014,"Russia_SK95",                52, 24.82,-131.21,-82.66,0,0,-0.16,-0.12, 0},
{ 6301, 1015,"Tokyo",                      10, -146.414, 507.337, 680.507,0,0,0,0,0},
{ 6123, 1016,"Kartastokoordinaattijarjestelma_1966", 4, -96.062, -82.428, -121.754, -4.801, -0.345, 1.376, 1.496, 0},
{ 6610, 1017,"Xian 1980",                  53, 24, -123, -94, -0.02, -0.25, 0.13, 1.1, 0},
{ 0,    1018,"Lithuanian Pulkovo 1942",    4, -40.59527, -18.54979, -69.33956, -2.508, -1.8319, 2.6114, -4.2991, 0},
{ 6313, 1019,"Belgian 1972 7 Parameter",   4, -99.059, 53.322, -112.486, -0.419, 0.83, -1.885, 0.999999, 0},
{ 6818, 1020,"S-JTSK with Ferro prime meridian", 10, 589, 76, 480, 0, 0, 0, 0, -17.666666666667},
{ 1031, 1021,"Serbia datum MGI 1901",      10, 574.027, 170.175, 401.545, 4.88786, -0.66524, -13.24673, 6.88933, 0},
{ 0,    1022,"North Sahara 7-parameter",   6, -38.7086, -128.8054, 118.8837, 0.83822, 7.38459, -1.57989, 3.9904, 0},
{ 0,    1023,"Hungarian Projection System (EOV) - updated", 21, 52.684, -71.194, -13.975, 0.312, 0.1063, 0.3729, 1.0191, 0 },
{ 1052, 1024,"S-JTSK (Krovak) Coordinate system - updated", 10, 570.6934, 85.6936, 462.8393, -4.99825, -1.58663, -5.26114, 3.5430155, 0 },
{ 0,    1025,"JTSK03 (Slovak Republic)",   10, 485.014055, 169.473618, 483.842943, -7.78625453, -4.39770887, -4.10248899, 0, 0 },
{ 1168, 1028,"Geocentric Datum of Australia 2020", 0,-0.06155, 0.01087, 0.04019, 0.0394924, 0.0327221, 0.0328979, 0.009994,0 },
{ 0,    9999,"Bosnia-Herzegovina",         10, 472.8677, 187.8769, 544.7084, -5.76198422, -5.3222842, 12.80666941, 1.54517287, 0 },
{ 6181, 9999,"Luxembourg 1930 / Gauss",     4, -192.986, 13.673, -39.309, 0.4099, 2.9332, -2.6881, 0.43, 0 },
{ 1168, 9999,"Geocentric Datum of Australia 2020", 0,-0.06155, 0.01087, 0.04019, 0.0394924, 0.0327221, 0.0328979, 0.009994,0 },

{ -1,   -1, nullptr,                          0, 0, 0, 0, 0, 0, 0, 0, 0}
};

/* -------------------------------------------------------------------- */
/*      This table was hand entered from Appendix I of the mapinfo 6    */
/*      manuals.                                                        */
/* -------------------------------------------------------------------- */

const MapInfoSpheroidInfo asSpheroidInfoList[] =
{
{ 9,"Airy 1930",                                6377563.396,    299.3249646},
{13,"Airy 1930 (modified for Ireland 1965",     6377340.189,    299.3249646},
{51,"ATS77 (Average Terrestrial System 1977)",  6378135,        298.257},
{ 2,"Australian",                               6378160.0,      298.25},
{10,"Bessel 1841",                              6377397.155,    299.1528128},
{35,"Bessel 1841 (modified for NGO 1948)",      6377492.0176,   299.15281},
{14,"Bessel 1841 (modified for Schwarzeck)",    6377483.865,    299.1528128},
{36,"Clarke 1858",                              6378293.639,    294.26068},
{ 7,"Clarke 1866",                              6378206.4,      294.9786982},
{ 8,"Clarke 1866 (modified for Michigan)",      6378450.047484481,294.9786982},
{ 6,"Clarke 1880",                              6378249.145,    293.465},
{15,"Clarke 1880 (modified for Arc 1950)",      6378249.145326, 293.4663076},
{30,"Clarke 1880 (modified for IGN)",           6378249.2,      293.4660213},
{37,"Clarke 1880 (modified for Jamaica)",       6378249.136,    293.46631},
{16,"Clarke 1880 (modified for Merchich)",      6378249.2,      293.46598},
{38,"Clarke 1880 (modified for Palestine)",     6378300.79,     293.46623},
{39,"Everest (Brunei and East Malaysia)",       6377298.556,    300.8017},
{11,"Everest (India 1830)",                     6377276.345,    300.8017},
{40,"Everest (India 1956)",                     6377301.243,    300.80174},
{50,"Everest (Pakistan)",                       6377309.613,    300.8017},
{17,"Everest (W. Malaysia and Singapore 1948)", 6377304.063,    300.8017},
{48,"Everest (West Malaysia 1969)",             6377304.063,    300.8017},
{18,"Fischer 1960",                             6378166.0,      298.3},
{19,"Fischer 1960 (modified for South Asia)",   6378155.0,      298.3},
{20,"Fischer 1968",                             6378150.0,      298.3},
{21,"GRS 67",                                   6378160.0,      298.247167427},
{ 0,"GRS 80",                                   6378137.0,      298.257222101},
{ 5,"Hayford",                                  6378388.0,      297.0},
{22,"Helmert 1906",                             6378200.0,      298.3},
{23,"Hough",                                    6378270.0,      297.0},
{31,"IAG 75",                                   6378140.0,      298.257222},
{41,"Indonesian",                               6378160.0,      298.247},
{ 4,"International 1924",                       6378388.0,      297.0},
{49,"Irish (WOFO)",                             6377542.178,    299.325},
{ 3,"Krassovsky",                               6378245.0,      298.3},
{32,"MERIT 83",                                 6378137.0,      298.257},
{33,"New International 1967",                   6378157.5,      298.25},
{42,"NWL 9D",                                   6378145.0,      298.25},
{43,"NWL 10D",                                  6378135.0,      298.26},
{44,"OSU86F",                                   6378136.2,      298.25722},
{45,"OSU91A",                                   6378136.3,      298.25722},
{46,"Plessis 1817",                             6376523.0,      308.64},
{52,"PZ90",                                     6378136.0,      298.257839303},
{24,"South American",                           6378160.0,      298.25},
{12,"Sphere",                                   6370997.0,      0.0},
{47,"Struve 1860",                              6378297.0,      294.73},
{34,"Walbeck",                                  6376896.0,      302.78},
{25,"War Office",                               6378300.583,    296.0},
{26,"WGS 60",                                   6378165.0,      298.3},
{27,"WGS 66",                                   6378145.0,      298.25},
{ 1,"WGS 72",                                   6378135.0,      298.26},
{28,"WGS 84",                                   6378137.0,      298.257223563},
{29,"WGS 84 (MAPINFO Datum 0)",                 6378137.01,     298.257223563},
{54,"WGS 84 (MAPINFO Datum 157)",               6378137.01,     298.257223563},
{-1,nullptr,                                       0.0,            0.0}
};

/* For LCC, standard parallel 1 and 2 can be switched indifferently */
/* So the MapInfo order and the EPSG order are not generally identical */
/* which may cause recognition problems when reading in MapInfo */
/* This table contains the parameters in the order expected by MapInfo */
typedef struct
{
    int    nEPSGCode;
    int    bReverseStdP;
    int    nMapInfoDatumID;
    double dfCenterLong;
    double dfCenterLat;
    double dfStdP1;
    double dfStdP2;
} MapInfoLCCSRS;

static const MapInfoLCCSRS asMapInfoLCCSRSList[] = {
{2154,1,33,3,46.5,44,49},
{2154,1,33,3,46.5,44,49.00000000001},
{2154,1,33,3,46.5,44,49.00000000002},
{2225,1,74,-122,39.3333333333,40,41.6666666667},
{2226,1,74,-122,37.6666666667,38.3333333333,39.8333333333},
{2227,1,74,-120.5,36.5,37.0666666667,38.4333333333},
{2228,1,74,-119,35.3333333333,36,37.25},
{2229,1,74,-118,33.5,34.0333333333,35.4666666667},
{2230,1,74,-116.25,32.1666666667,32.7833333333,33.8833333333},
{2231,1,74,-105.5,39.3333333333,39.7166666667,40.7833333333},
{2232,1,74,-105.5,37.8333333333,38.45,39.75},
{2233,1,74,-105.5,36.6666666667,37.2333333333,38.4333333333},
{2234,1,74,-72.75,40.8333333333,41.2,41.8666666667},
{2238,1,74,-84.5,29,29.5833333333,30.75},
{2246,0,74,-84.25,37.5,37.9666666667,38.9666666667},
{2247,1,74,-85.75,36.3333333333,36.7333333333,37.9333333333},
{2248,1,74,-77,37.6666666667,38.3,39.45},
{2249,1,74,-71.5,41,41.7166666667,42.6833333333},
{2250,1,74,-70.5,41,41.2833333333,41.4833333333},
{2251,1,74,-87,44.7833333333,45.4833333333,47.0833333333},
{2252,1,74,-84.3666666667,43.3166666667,44.1833333333,45.7},
{2253,1,74,-84.3666666667,41.5,42.1,43.6666666667},
{2256,1,74,-109.5,44.25,45,49},
{2263,1,74,-74,40.1666666667,40.6666666667,41.0333333333},
{2264,1,74,-79,33.75,34.3333333333,36.1666666667},
{2265,1,74,-100.5,47,47.4333333333,48.7333333333},
{2266,1,74,-100.5,45.6666666667,46.1833333333,47.4833333333},
{2267,1,74,-98,35,35.5666666667,36.7666666667},
{2268,1,74,-98,33.3333333333,33.9333333333,35.2333333333},
{2269,1,74,-120.5,43.6666666667,44.3333333333,46},
{2270,1,74,-120.5,41.6666666667,42.3333333333,44},
{2271,1,74,-77.75,40.1666666667,40.8833333333,41.95},
{2272,1,74,-77.75,39.3333333333,39.9333333333,40.9666666667},
{2273,1,74,-81,31.8333333333,32.5,34.8333333333},
{2274,1,74,-86,34.3333333333,35.25,36.4166666667},
{2275,1,74,-101.5,34,34.65,36.1833333333},
{2276,1,74,-98.5,31.6666666667,32.1333333333,33.9666666667},
{2277,1,74,-100.3333333333,29.6666666667,30.1166666667,31.8833333333},
{2278,1,74,-99,27.8333333333,28.3833333333,30.2833333333},
{2279,1,74,-98.5,25.6666666667,26.1666666667,27.8333333333},
{2280,1,74,-111.5,40.3333333333,40.7166666667,41.7833333333},
{2281,1,74,-111.5,38.3333333333,39.0166666667,40.65},
{2282,1,74,-111.5,36.6666666667,37.2166666667,38.35},
{2283,1,74,-78.5,37.6666666667,38.0333333333,39.2},
{2284,1,74,-78.5,36.3333333333,36.7666666667,37.9666666667},
{2285,1,74,-120.8333333333,47,47.5,48.7333333333},
{2286,1,74,-120.5,45.3333333333,45.8333333333,47.3333333333},
{2287,1,74,-90,45.1666666667,45.5666666667,46.7666666667},
{2288,1,74,-90,43.8333333333,44.25,45.5},
{2289,1,74,-90,42,42.7333333333,44.0666666667},
{26740,1,63,-176,51,51.8333333333,53.8333333333},
{26741,1,62,-122,39.3333333333,40,41.6666666667},
{26742,1,62,-122,37.6666666667,38.3333333333,39.8333333333},
{26743,1,62,-120.5,36.5,37.0666666667,38.4333333333},
{26744,1,62,-119,35.3333333333,36,37.25},
{26745,1,62,-118,33.5,34.0333333333,35.4666666667},
{26746,1,62,-116.25,32.1666666667,32.7833333333,33.8833333333},
{26747,1,62,-118.3333333333,34.1333333333,33.8666666667,34.4166666667},
{26751,1,62,-92,34.3333333333,34.9333333333,36.2333333333},
{26752,1,62,-92,32.6666666667,33.3,34.7666666667},
{26753,0,62,-105.5,39.3333333333,39.7166666667,40.7833333333},
{26754,1,62,-105.5,37.8333333333,38.45,39.75},
{26755,1,62,-105.5,36.6666666667,37.2333333333,38.4333333333},
{26756,1,62,-72.75,40.8333333333,41.2,41.8666666667},
{26760,1,62,-84.5,29,29.5833333333,30.75},
{26775,1,62,-93.5,41.5,42.0666666667,43.2666666667},
{26776,1,62,-93.5,40,40.6166666667,41.7833333333},
{26777,1,62,-98,38.3333333333,38.7166666667,39.7833333333},
{26778,0,62,-98.5,36.6666666667,38.5666666667,37.2666666667},
{26779,0,62,-84.25,37.5,37.9666666667,38.9666666667},
{26780,0,62,-85.75,36.3333333333,36.7333333333,37.9333333333},
{26781,0,62,-92.5,30.6666666667,31.1666666667,32.6666666667},
{26785,0,62,-77,37.8333333333,38.3,39.45},
{26786,0,62,-71.5,41,41.7166666667,42.6833333333},
{26788,0,73,-87,44.7833333333,45.4833333333,47.0833333333},
{26789,0,73,-84.3333333333,43.3166666667,44.1833333333,45.7},
{26790,0,73,-84.3333333333,41.5,42.1,43.6666666667},
{26791,0,62,-93.1,46.5,47.0333333333,48.6333333333},
{26792,0,62,-94.25,45,45.6166666667,47.05},
{26793,0,62,-94,43,43.7833333333,45.2166666667},
{26940,1,74,-176,51,51.8333333333,53.8333333333},
{26941,1,74,-122,39.3333333333,40,41.6666666667},
{26942,1,74,-122,37.6666666667,38.3333333333,39.8333333333},
{26943,1,74,-120.5,36.5,37.0666666667,38.4333333333},
{26944,1,74,-119,35.3333333333,36,37.25},
{26945,1,74,-118,33.5,34.0333333333,35.4666666667},
{26946,1,74,-116.25,32.1666666667,32.7833333333,33.8833333333},
{26951,1,74,-92,34.3333333333,34.9333333333,36.2333333333},
{26952,1,74,-92,32.6666666667,33.3,34.7666666667},
{26953,1,74,-105.5,39.3333333333,39.7166666667,40.7833333333},
{26954,1,74,-105.5,37.8333333333,38.45,39.75},
{26955,1,74,-105.5,36.6666666667,37.2333333333,38.4333333333},
{26956,1,74,-72.75,40.8333333333,41.2,41.8666666667},
{26960,1,74,-84.5,29,29.5833333333,30.75},
{26975,1,74,-93.5,41.5,42.0666666667,43.2666666667},
{26976,1,74,-93.5,40,40.6166666667,41.7833333333},
{26977,1,74,-98,38.3333333333,38.7166666667,39.7833333333},
{26978,0,74,-98.5,36.6666666667,38.5666666667,37.2666666667},
{26980,1,74,-85.75,36.3333333333,36.7333333333,37.9333333333},
{26981,1,74,-92.5,30.5,31.1666666667,32.6666666667},
{26982,1,74,-91.3333333333,28.5,29.3,30.7},
{26985,1,74,-77,37.6666666667,38.3,39.45},
{26986,1,74,-71.5,41,41.7166666667,42.6833333333},
{26987,1,74,-70.5,41,41.2833333333,41.4833333333},
{26988,1,74,-87,44.7833333333,45.4833333333,47.0833333333},
{26989,1,74,-84.3666666667,43.3166666667,44.1833333333,45.7},
{26990,1,74,-84.3666666667,41.5,42.1,43.6666666667},
{26991,1,74,-93.1,46.5,47.0333333333,48.6333333333},
{26992,1,74,-94.25,45,45.6166666667,47.05},
{26993,1,74,-94,43,43.7833333333,45.2166666667},
{3111,0,116,145,-37,-36,-38},
{31370,1,1019,4.3674866667,90,49.8333339000,51.1666672333},
{32001,1,62,-109.5,47,47.85,48.7166666667},
{32002,1,62,-109.5,45.8333333333,46.45,47.8833333333},
{32003,1,62,-109.5,44,44.8666666667,46.4},
{32005,0,62,-100,41.3333333333,41.85,42.8166666667},
{32006,0,62,-99.5,39.6666666667,40.2833333333,41.7166666667},
{32018,1,62,-74,40.5,40.6666666667,41.0333333333},
{32019,0,62,-79,33.75,34.3333333333,36.1666666667},
{32020,0,62,-100.5,47,47.4333333333,48.7333333333},
{32021,0,62,-100.5,45.6666666667,46.1833333333,47.4833333333},
{32022,0,62,-82.5,39.6666666667,40.4333333333,41.7},
{32023,0,62,-82.5,38,38.7333333333,40.0333333333},
{32024,0,62,-98,35,35.5666666667,36.7666666667},
{32025,0,62,-98,33.3333333333,33.9333333333,35.2333333333},
{32026,0,62,-120.5,43.6666666667,44.3333333333,46},
{32027,0,62,-120.5,41.6666666667,42.3333333333,44},
{32028,0,62,-77.75,40.1666666667,40.8833333333,41.95},
{32031,0,62,-81,33,33.7666666667,34.9666666667},
{32033,0,62,-81,31.8333333333,32.3333333333,33.6666666667},
{32034,0,62,-100,43.8333333333,44.4166666667,45.6833333333},
{32035,0,62,-100.3333333333,42.3333333333,42.8333333333,44.4},
{32036,0,62,-86,34.6666666667,35.25,36.4166666667},
{32037,0,62,-101.5,34,34.65,36.1833333333},
{32038,0,62,-97.5,31.6666666667,32.1333333333,33.9666666667},
{32039,0,62,-100.3333333333,29.6666666667,30.1166666667,31.8833333333},
{32040,0,62,-99,27.8333333333,28.3833333333,30.2833333333},
{32041,0,62,-98.5,25.6666666667,26.1666666667,27.8333333333},
{32042,0,62,-111.5,40.3333333333,40.7166666667,41.7833333333},
{32043,0,62,-111.5,38.3333333333,39.0166666667,40.65},
{32044,0,62,-111.5,36.6666666667,37.2166666667,38.35},
{32046,0,62,-78.5,37.6666666667,38.0333333333,39.2},
{32047,0,62,-78.5,36.3333333333,36.7666666667,37.9666666667},
{32048,0,62,-120.8333333333,47,47.5,48.7333333333},
{32049,0,62,-120.5,45.3333333333,45.8333333333,47.3333333333},
{32050,0,62,-79.5,38.5,39,40.25},
{32051,0,62,-81,37,37.4833333333,38.8833333333},
{32052,0,62,-90,45.1666666667,45.5666666667,46.7666666667},
{32053,0,62,-90,43.8333333333,44.25,45.5},
{32054,0,62,-90,42,42.7333333333,44.0666666667},
{32059,0,62,-66.4333333333,18.4333333333,18.0333333333,18.4333333333},
{32060,0,62,-66.4333333333,18.4333333333,18.0333333333,18.4333333333},
{32100,1,74,-109.5,44.25,45,49},
{32104,1,74,-100,39.8333333333,40,43},
{32118,1,74,-74,40.1666666667,40.6666666667,41.0333333333},
{32119,1,74,-79,33.75,34.3333333333,36.1666666667},
{32120,1,74,-100.5,47,47.4333333333,48.7333333333},
{32121,1,74,-100.5,45.6666666667,46.1833333333,47.4833333333},
{32122,1,74,-82.5,39.6666666667,40.4333333333,41.7},
{32123,1,74,-82.5,38,38.7333333333,40.0333333333},
{32124,1,74,-98,35,35.5666666667,36.7666666667},
{32125,1,74,-98,33.3333333333,33.9333333333,35.2333333333},
{32126,1,74,-120.5,43.6666666667,44.3333333333,46},
{32127,1,74,-120.5,41.6666666667,42.3333333333,44},
{32128,1,74,-77.75,40.1666666667,40.8833333333,41.95},
{32129,1,74,-77.75,39.3333333333,39.9333333333,40.9666666667},
{32133,1,74,-81,31.8333333333,32.5,34.8333333333},
{32134,1,74,-100,43.8333333333,44.4166666667,45.6833333333},
{32135,1,74,-100.3333333333,42.3333333333,42.8333333333,44.4},
{32136,1,74,-86,34.3333333333,35.25,36.4166666667},
{32137,1,74,-101.5,34,34.65,36.1833333333},
{32138,1,74,-98.5,31.6666666667,32.1333333333,33.9666666667},
{32139,1,74,-100.3333333333,29.6666666667,30.1166666667,31.8833333333},
{32140,1,74,-99,27.8333333333,28.3833333333,30.2833333333},
{32141,1,74,-98.5,25.6666666667,26.1666666667,27.8333333333},
{32142,1,74,-111.5,40.3333333333,40.7166666667,41.7833333333},
{32143,1,74,-111.5,38.3333333333,39.0166666667,40.65},
{32144,1,74,-111.5,36.6666666667,37.2166666667,38.35},
{32146,1,74,-78.5,37.6666666667,38.0333333333,39.2},
{32147,1,74,-78.5,36.3333333333,36.7666666667,37.9666666667},
{32148,1,74,-120.8333333333,47,47.5,48.7333333333},
{32149,1,74,-120.5,45.3333333333,45.8333333333,47.3333333333},
{32150,1,74,-79.5,38.5,39,40.25},
{32151,1,74,-81,37,37.4833333333,38.8833333333},
{32152,1,74,-90,45.1666666667,45.5666666667,46.7666666667},
{32153,1,74,-90,43.8333333333,44.25,45.5},
{32154,1,74,-90,42,42.7333333333,44.0666666667},
{32161,1,74,-66.4333333333,17.8333333333,18.0333333333,18.4333333333},
{3300,1,115,24,57.51755394,58,59.33333333},
{3301,1,115,24,57.51755393056,58,59.33333333},
{3797,0,66,-70,44,50,46},
{3798,0,74,-70,44,50,46},
{3799,0,74,-70,44,50,46},
{3942,0,33,3,42,41.25,42.75},
{3943,0,33,3,43,42.25,43.75},
{3944,0,33,3,44,43.25,44.75},
{3945,0,33,3,45,44.25,45.75},
{3946,0,33,3,46,45.25,46.75},
{3947,0,33,3,47,46.25,47.75},
{3948,0,33,3,48,47.25,48.75},
{3949,0,33,3,49,48.25,49.75},
{3950,0,33,3,50,49.25,50.75},
{42101,0,104,-95,0,49,77},
{42103,0,104,-100,0,33,45},
{42304,0,74,-95,49,49,77},
{0,0,0,110,10,25,40},
{0,0,0,132.5,-10,-21.5,-33.5},
{0,0,0,25,35,40,65},
{0,0,0,47.5,25,15,35},
{0,0,0,95,40,20,60},
{0,0,1002,0,42.165,41.5603877778,42.76766333},
{0,0,1002,0,42.165,41.5603877778,42.767663333},
{0,0,1002,0,42.165,41.560387778,42.76766333},
{0,0,1002,0,42.165,41.560387778,42.767663333},
{0,0,1002,0,42.165,41.56038778,42.76766333},
{0,0,1002,0,42.165,41.560387840948,42.76766346965},
{0,0,1002,0,44.1,43.199291275544,44.996093814511},
{0,0,1002,0,44.1,43.1992913889,44.99609389},
{0,0,1002,0,44.1,43.199291389,44.99609389},
{0,0,1002,0,44.1,43.19929139,44.99609389},
{0,0,1002,0,46.8,45.8989188889,47.69601444},
{0,0,1002,0,46.8,45.898918889,47.69601444},
{0,0,1002,0,46.8,45.89891889,47.69601444},
{0,0,1002,0,46.8,45.898918964419,47.696014502038},
{0,0,1002,0,49.5,48.5985227778,50.39591167},
{0,0,1002,0,49.5,48.598522778,50.39591167},
{0,0,1002,0,49.5,48.59852278,50.39591167},
{0,0,1002,0,49.5,48.598522847174,50.395911631678},
{0,0,1005,23,-23,-18,-32},
{0,0,1022,2.7,36,37.575,34.425},
{0,0,104,13.33333333,47.5,46,49},
{0,0,104,13.33333333,48,46,49},
{0,0,104,-19,65,64.25,65.75},
{0,0,104,36.0,25.0,37.5,40.5},
{0,0,104,36,25,37.5,40.5},
{0,0,104,70,-50,-68.5,-74.5},
{0,0,110,4.367975,90,49.8333333333,51.1666666667},
{0,0,115,10,52,35,45},
{0,0,116,135,-24,-18,-36},
{0,0,116,135,-32,-28,-36},
{0,0,12,135,-24,-18,-36},
{0,0,12,145,-37,-36,-38},
{0,0,13,135,-24,-18,-36},
{0,0,19,23,-23,-18,-32},
{0,0,28,17,29.77930555,42,56},
{0,0,28,19,29.77930555,42,56},
{0,0,28,36.0,25.0,37.5,40.5},
{0,0,33,13.5,0,52.6666666667,55.3333333333},
{0,0,33,15,0,56.5,60.5},
{0,0,33,15,0,58,66},
{0,0,33,15,0,63.5,67.5},
{0,0,33,15.5,0,56.6666666667,59.3333333333},
{0,0,33,15.5,0,60.6666666667,63.3333333333},
{0,0,33,16.5,0,60.6666666667,63.3333333333},
{0,0,33,18.5,0,64.6666666667,67.3333333333},
{0,0,33,19,0,64.6666666667,67.3333333333},
{0,0,55,-5.4,22.5,20.9075742561,24.0921050540},
{0,0,55,-5.4,26.1,24.5075340813,27.6921073632},
{0,0,55,-5.4,29.7,28.1063294800,31.2932791054},
{0,0,55,-5.4,33.3,31.72786641202,34.8717272112},
{0,0,62,-70.5,41,41.2833333333,41.4833333333},
{0,0,62,-77.75,39.3333333333,39.9333333333,40.9666666667},
{0,0,62,-91.3333333333,25.6666666667,26.1666666667,27.8333333333},
{0,0,62,-91.3333333333,28.6666666667,29.3,30.67},
{0,0,62,-96,23,20,60},
{0,0,62,-96,23,33,45},
{0,0,62,-96,39,33,45},
{0,0,66,-68.5,44,46,60},
{0,0,74,-100.3333333333,42.3333333333,42.8333333333,44.4},
{0,0,74,-100,39.8333333333,40,43},
{0,0,74,-100,43.8333333333,44.4166666667,45.6833333333},
{0,0,74,-109.5,44.25,45,49},
{0,0,74,-111.5,36.6666666667,37.2166666667,38.35},
{0,0,74,-111.5,38.3333333333,39.0166666667,40.65},
{0,0,74,-111.5,40.3333333333,40.7166666667,41.7833333333},
{0,0,74,-120.5,41.6666666667,42.3333333333,44},
{0,0,74,-120.5,43.6666666667,44.3333333333,46},
{0,0,74,-176,51,51.8333333333,53.8333333333},
{0,0,74,-66.4333333333,17.8333333333,18.0333333333,18.4333333333},
{0,0,74,-68.5,44,46,60},
{0,0,74,-79.5,38.5,39,40.25},
{0,0,74,-81,31.8333333333,32.5,34.8333333333},
{0,0,74,-81,37,37.4833333333,38.8833333333},
{0,0,74,-82.5,38,38.7333333333,40.0333333333},
{0,0,74,-82.5,39.6666666667,40.4333333333,41.7},
{0,0,74,-84.25,37.5,37.9666666667,38.9666666667},
{0,0,74,-84.3666666667,41.5,42.1,43.6666666667},
{0,0,74,-84.3666666667,43.3166666667,44.1833333333,45.7},
{0,0,74,-87,44.7833333333,45.4833333333,47.0833333333},
{0,0,74,-91.3333333333,25.5,26.1666666667,27.8333333333},
{0,0,74,-91.3333333333,28.5,29.3,30.7},
{0,0,74,-92,32.6666666667,33.3,34.7666666667},
{0,0,74,-92,34.3333333333,34.9333333333,36.2333333333},
{0,0,74,-92.5,30.5,31.1666666667,32.6666666667},
{0,0,74,-93.1,46.5,47.0333333333,48.6333333333},
{0,0,74,-93.5,40,40.6166666667,41.7833333333},
{0,0,74,-93.5,41.5,42.0666666667,43.2666666667},
{0,0,74,-94.25,45,45.6166666667,47.05},
{0,0,74,-94,43,43.7833333333,45.2166666667},
{0,0,74,-98,38.3333333333,38.7166666667,39.7833333333},
{0,0,74,-98.5,36.6666666667,38.5666666667,37.2666666667},
};

/**********************************************************************
 *                   TABFile::GetSpatialRef()
 *
 * Returns a reference to an OGRSpatialReference for this dataset.
 * If the projection parameters have not been parsed yet, then we will
 * parse them before returning.
 *
 * The returned object is owned and maintained by this TABFile and
 * should not be modified or freed by the caller.
 *
 * Returns NULL if the SpatialRef cannot be accessed.
 **********************************************************************/
OGRSpatialReference *TABFile::GetSpatialRef()
{
    if (m_poMAPFile == nullptr )
    {
        CPLError(CE_Failure, CPLE_AssertionFailed,
                 "GetSpatialRef() failed: file has not been opened yet.");
        return nullptr;
    }

    if( GetGeomType() == wkbNone )
        return nullptr;

    /*-----------------------------------------------------------------
     * If projection params have already been processed, just use them.
     *----------------------------------------------------------------*/
    if (m_poSpatialRef != nullptr)
        return m_poSpatialRef;

    /*-----------------------------------------------------------------
     * Fetch the parameters from the header.
     *----------------------------------------------------------------*/
    TABProjInfo sTABProj;

    TABMAPHeaderBlock *poHeader = nullptr;
    if ((poHeader = m_poMAPFile->GetHeaderBlock()) == nullptr ||
        poHeader->GetProjInfo( &sTABProj ) != 0)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "GetSpatialRef() failed reading projection parameters.");
        return nullptr;
    }

    m_poSpatialRef = GetSpatialRefFromTABProj(sTABProj);
    return m_poSpatialRef;
}

/**********************************************************************
 *                   TABFile::GetSpatialRefFromTABProj()
 **********************************************************************/

static bool TAB_EQUAL( double a, double b )
{
    // TODO(schwehr): Use std::abs.
    return (a < b ? (b - a) : (a - b)) < 1.0e-10;
}

OGRSpatialReference* TABFile::GetSpatialRefFromTABProj(const TABProjInfo& sTABProj)
{
    /*-----------------------------------------------------------------
     * Get the units name, and translation factor.
     *----------------------------------------------------------------*/
    const char *pszUnitsName = nullptr;
    const char *pszUnitsConv = nullptr;
    /* double      dfConv = 1.0; */

    switch( sTABProj.nUnitsId )
    {
      case 0:
        pszUnitsName = "Mile";
        pszUnitsConv = "1609.344";
        break;

      case 1:
        pszUnitsName = "Kilometer";
        pszUnitsConv = "1000.0";
        break;

      case 2:
        pszUnitsName = "IINCH";
        pszUnitsConv = "0.0254";
        break;

      case 3:
        pszUnitsName = SRS_UL_FOOT;
        pszUnitsConv = SRS_UL_FOOT_CONV;
        break;

      case 4:
        pszUnitsName = "IYARD";
        pszUnitsConv = "0.9144";
        break;

      case 5:
        pszUnitsName = "Millimeter";
        pszUnitsConv = "0.001";
        break;

      case 6:
        pszUnitsName = "Centimeter";
        pszUnitsConv = "0.01";
        break;

      case 7:
        pszUnitsName = SRS_UL_METER;
        pszUnitsConv = "1.0";
        break;

      case 8:
        pszUnitsName = SRS_UL_US_FOOT;
        pszUnitsConv = SRS_UL_US_FOOT_CONV;
        break;

      case 9:
        pszUnitsName = SRS_UL_NAUTICAL_MILE;
        pszUnitsConv = SRS_UL_NAUTICAL_MILE_CONV;
        break;

      case 30:
        pszUnitsName = SRS_UL_LINK;
        pszUnitsConv = SRS_UL_LINK_CONV;
        break;

      case 31:
        pszUnitsName = SRS_UL_CHAIN;
        pszUnitsConv = SRS_UL_CHAIN_CONV;
        break;

      case 32:
        pszUnitsName = SRS_UL_ROD;
        pszUnitsConv = SRS_UL_ROD_CONV;
        break;

      default:
        pszUnitsName = SRS_UL_METER;
        pszUnitsConv = "1.0";
        break;
    }

    /* dfConv = CPLAtof(pszUnitsConv); */

    /*-----------------------------------------------------------------
     * Transform them into an OGRSpatialReference.
     *----------------------------------------------------------------*/
    OGRSpatialReference* poSpatialRef = new OGRSpatialReference;
    poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

    /*-----------------------------------------------------------------
     * Handle the PROJCS style projections, but add the datum later.
     *----------------------------------------------------------------*/
    switch( sTABProj.nProjId )
    {
      case 0:
        poSpatialRef->SetLocalCS( "Nonearth" );
        poSpatialRef->SetLinearUnits(pszUnitsName, CPLAtof(pszUnitsConv));
        break;

        /*--------------------------------------------------------------
         * lat/long .. just add the GEOGCS later.
         *-------------------------------------------------------------*/
      case 1:
        break;

        /*--------------------------------------------------------------
         * Cylindrical Equal Area
         *-------------------------------------------------------------*/
      case 2:
        poSpatialRef->SetCEA( sTABProj.adProjParams[1],
                                sTABProj.adProjParams[0],
                                sTABProj.adProjParams[2],
                                sTABProj.adProjParams[3] );
        break;

        /*--------------------------------------------------------------
         * Lambert Conic Conformal
         *-------------------------------------------------------------*/
      case 3:
        poSpatialRef->SetLCC( sTABProj.adProjParams[2],
                                sTABProj.adProjParams[3],
                                sTABProj.adProjParams[1],
                                sTABProj.adProjParams[0],
                                sTABProj.adProjParams[4],
                                sTABProj.adProjParams[5] );
        break;

        /*--------------------------------------------------------------
         * Lambert Azimuthal Equal Area
         *-------------------------------------------------------------*/
      case 4:
      case 29:
        poSpatialRef->SetLAEA( sTABProj.adProjParams[1],
                                 sTABProj.adProjParams[0],
                                 0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Azimuthal Equidistant (Polar aspect only)
         *-------------------------------------------------------------*/
      case 5:
      case 28:
        poSpatialRef->SetAE( sTABProj.adProjParams[1],
                               sTABProj.adProjParams[0],
                               0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Equidistant Conic
         *-------------------------------------------------------------*/
      case 6:
        poSpatialRef->SetEC( sTABProj.adProjParams[2],
                               sTABProj.adProjParams[3],
                               sTABProj.adProjParams[1],
                               sTABProj.adProjParams[0],
                               sTABProj.adProjParams[4],
                               sTABProj.adProjParams[5] );
        break;

        /*--------------------------------------------------------------
         * Hotine Oblique Mercator
         *-------------------------------------------------------------*/
      case 7:
        poSpatialRef->SetHOM( sTABProj.adProjParams[1],
                                sTABProj.adProjParams[0],
                                sTABProj.adProjParams[2],
                                90.0,
                                sTABProj.adProjParams[3],
                                sTABProj.adProjParams[4],
                                sTABProj.adProjParams[5] );
        break;

        /*--------------------------------------------------------------
         * Transverse Mercator
         *-------------------------------------------------------------*/
      case 8:
        poSpatialRef->SetTM( sTABProj.adProjParams[1],
                               sTABProj.adProjParams[0],
                               sTABProj.adProjParams[2],
                               sTABProj.adProjParams[3],
                               sTABProj.adProjParams[4] );
        break;

        /*----------------------------------------------------------------
         * Transverse Mercator,(modified for Danish System 34 Jylland-Fyn)
         *---------------------------------------------------------------*/
      case 21:
         //poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_21,
         poSpatialRef->SetTM(
                                       sTABProj.adProjParams[1],
                                       sTABProj.adProjParams[0],
                                       sTABProj.adProjParams[2],
                                       sTABProj.adProjParams[3],
                                       sTABProj.adProjParams[4] );
         break;

        /*--------------------------------------------------------------
         * Transverse Mercator,(modified for Danish System 34 Sjaelland)
         *-------------------------------------------------------------*/
      case 22:
         //poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_22,
         poSpatialRef->SetTM(
                                       sTABProj.adProjParams[1],
                                       sTABProj.adProjParams[0],
                                       sTABProj.adProjParams[2],
                                       sTABProj.adProjParams[3],
                                       sTABProj.adProjParams[4] );
         break;

        /*----------------------------------------------------------------
         * Transverse Mercator,(modified for Danish System 34/45 Bornholm)
         *---------------------------------------------------------------*/
      case 23:
         //poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_23,
         poSpatialRef->SetTM(
                                       sTABProj.adProjParams[1],
                                       sTABProj.adProjParams[0],
                                       sTABProj.adProjParams[2],
                                       sTABProj.adProjParams[3],
                                       sTABProj.adProjParams[4] );
         break;

        /*--------------------------------------------------------------
         * Transverse Mercator,(modified for Finnish KKJ)
         *-------------------------------------------------------------*/
      case 24:
         //poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_24,
        poSpatialRef->SetTM(
                                       sTABProj.adProjParams[1],
                                       sTABProj.adProjParams[0],
                                       sTABProj.adProjParams[2],
                                       sTABProj.adProjParams[3],
                                       sTABProj.adProjParams[4] );
         break;

        /*--------------------------------------------------------------
         * Albers Conic Equal Area
         *-------------------------------------------------------------*/
      case 9:
        poSpatialRef->SetACEA( sTABProj.adProjParams[2],
                                 sTABProj.adProjParams[3],
                                 sTABProj.adProjParams[1],
                                 sTABProj.adProjParams[0],
                                 sTABProj.adProjParams[4],
                                 sTABProj.adProjParams[5] );
        break;

        /*--------------------------------------------------------------
         * Mercator
         *-------------------------------------------------------------*/
      case 10:
        poSpatialRef->SetMercator( 0.0, sTABProj.adProjParams[0],
                                     1.0, 0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Miller Cylindrical
         *-------------------------------------------------------------*/
      case 11:
        poSpatialRef->SetMC( 0.0, sTABProj.adProjParams[0],
                               0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Robinson
         *-------------------------------------------------------------*/
      case 12:
        poSpatialRef->SetRobinson( sTABProj.adProjParams[0],
                                     0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Mollweide
         *-------------------------------------------------------------*/
      case 13:
        poSpatialRef->SetMollweide( sTABProj.adProjParams[0],
                                      0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Eckert IV
         *-------------------------------------------------------------*/
      case 14:
        poSpatialRef->SetEckertIV( sTABProj.adProjParams[0], 0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Eckert VI
         *-------------------------------------------------------------*/
      case 15:
        poSpatialRef->SetEckertVI( sTABProj.adProjParams[0], 0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Sinusoidal
         *-------------------------------------------------------------*/
      case 16:
        poSpatialRef->SetSinusoidal( sTABProj.adProjParams[0],
                                       0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Gall Stereographic
         *-------------------------------------------------------------*/
      case 17:
        poSpatialRef->SetGS( sTABProj.adProjParams[0], 0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * New Zealand Map Grid
         *-------------------------------------------------------------*/
      case 18:
        poSpatialRef->SetNZMG( sTABProj.adProjParams[1],
                                 sTABProj.adProjParams[0],
                                 sTABProj.adProjParams[2],
                                 sTABProj.adProjParams[3] );
        break;

        /*--------------------------------------------------------------
         * Lambert Conic Conformal (Belgium)
         *-------------------------------------------------------------*/
      case 19:
        poSpatialRef->SetLCCB( sTABProj.adProjParams[2],
                                 sTABProj.adProjParams[3],
                                 sTABProj.adProjParams[1],
                                 sTABProj.adProjParams[0],
                                 sTABProj.adProjParams[4],
                                 sTABProj.adProjParams[5] );
        break;

        /*--------------------------------------------------------------
         * Stereographic
         *-------------------------------------------------------------*/
      case 20:
        poSpatialRef->SetStereographic( sTABProj.adProjParams[1],
                                          sTABProj.adProjParams[0],
                                          sTABProj.adProjParams[2],
                                          sTABProj.adProjParams[3],
                                          sTABProj.adProjParams[4] );
        break;

        /*--------------------------------------------------------------
         * Swiss Oblique Mercator / Cylindrical
         *-------------------------------------------------------------*/
      case 25:
        poSpatialRef->SetSOC( sTABProj.adProjParams[1],
                                sTABProj.adProjParams[0],
                                sTABProj.adProjParams[2],
                                sTABProj.adProjParams[3] );
        break;

        /*--------------------------------------------------------------
         * Regional Mercator (regular mercator with a latitude).
         *-------------------------------------------------------------*/
      case 26:
        poSpatialRef->SetMercator2SP( sTABProj.adProjParams[1],
                                      0.0,
                                      sTABProj.adProjParams[0],
                                      0.0, 0.0 );
        break;

        /*--------------------------------------------------------------
         * Polyconic
         *-------------------------------------------------------------*/
      case 27:
        poSpatialRef->SetPolyconic( sTABProj.adProjParams[1],
                                      sTABProj.adProjParams[0],
                                      sTABProj.adProjParams[2],
                                      sTABProj.adProjParams[3] );
        break;

        /*--------------------------------------------------------------
         * Cassini/Soldner
         *-------------------------------------------------------------*/
      case 30:
        poSpatialRef->SetCS( sTABProj.adProjParams[1],
                               sTABProj.adProjParams[0],
                               sTABProj.adProjParams[2],
                               sTABProj.adProjParams[3] );
        break;

        /*--------------------------------------------------------------
         * Oblique Stereographic
         *-------------------------------------------------------------*/
      case 31:
        poSpatialRef->SetOS( sTABProj.adProjParams[1],
                                          sTABProj.adProjParams[0],
                                          sTABProj.adProjParams[2],
                                          sTABProj.adProjParams[3],
                                          sTABProj.adProjParams[4] );
        break;

     /*--------------------------------------------------------------
      * Krovak
      *-------------------------------------------------------------*/
      case 32:
        poSpatialRef->SetKrovak( sTABProj.adProjParams[1],   // dfCenterLat
                                   sTABProj.adProjParams[0],   // dfCenterLong
                                   sTABProj.adProjParams[3],   // dfAzimuth
                                   sTABProj.adProjParams[2],   // dfPseudoStdParallelLat
                                   1.0,                        // dfScale
                                   sTABProj.adProjParams[4],   // dfFalseEasting
                                   sTABProj.adProjParams[5] ); // dfFalseNorthing
        break;

     /*--------------------------------------------------------------
      * Equidistant Cylindrical / Equirectangular
      *-------------------------------------------------------------*/
      case 33:
        poSpatialRef->SetEquirectangular( sTABProj.adProjParams[1],
                                            sTABProj.adProjParams[0],
                                            sTABProj.adProjParams[2],
                                            sTABProj.adProjParams[3] );
        break;

      default:
        break;
    }

    /*-----------------------------------------------------------------
     * Collect units definition.
     *----------------------------------------------------------------*/
    if( sTABProj.nProjId != 0 && sTABProj.nProjId != 1 && CPLAtof( pszUnitsConv ) != 1 )
    {
        poSpatialRef->SetTargetLinearUnits(nullptr,
                                           pszUnitsName,
                                           CPLAtof( pszUnitsConv ));
    }

    /*-----------------------------------------------------------------
     * Local (nonearth) coordinate systems have no Geographic relationship
     * so we just return from here.
     *----------------------------------------------------------------*/
    if( sTABProj.nProjId == 0 )
        return poSpatialRef;

    /*-----------------------------------------------------------------
     * Set the datum.  We are only given the X, Y and Z shift for
     * the datum, so for now we just synthesize a name from this.
     * It would be better if we could lookup a name based on the shift.
     *
     * Since we have already encountered files in which adDatumParams[] values
     * were in the order of 1e-150 when they should have actually been zeros,
     * we will use an epsilon in our scan instead of looking for equality.
     *----------------------------------------------------------------*/
    const MapInfoDatumInfo *psDatumInfo = nullptr;

    for( int iDatumInfo = 0;
         asDatumInfoList[iDatumInfo].nMapInfoDatumID != -1;
         iDatumInfo++ )
    {
        psDatumInfo = asDatumInfoList + iDatumInfo;

        if( TAB_EQUAL(psDatumInfo->nEllipsoid, sTABProj.nEllipsoidId) &&
            ((sTABProj.nDatumId > 0 &&
              sTABProj.nDatumId == psDatumInfo->nMapInfoDatumID) ||
             (sTABProj.nDatumId <= 0
              && TAB_EQUAL(psDatumInfo->dfShiftX, sTABProj.dDatumShiftX)
              && TAB_EQUAL(psDatumInfo->dfShiftY, sTABProj.dDatumShiftY)
              && TAB_EQUAL(psDatumInfo->dfShiftZ, sTABProj.dDatumShiftZ)
              && TAB_EQUAL(psDatumInfo->dfDatumParm0,sTABProj.adDatumParams[0])
              && TAB_EQUAL(psDatumInfo->dfDatumParm1,sTABProj.adDatumParams[1])
              && TAB_EQUAL(psDatumInfo->dfDatumParm2,sTABProj.adDatumParams[2])
              && TAB_EQUAL(psDatumInfo->dfDatumParm3,sTABProj.adDatumParams[3])
              && TAB_EQUAL(psDatumInfo->dfDatumParm4,sTABProj.adDatumParams[4]))))
            break;

        psDatumInfo = nullptr;
    }

    char szDatumName[200] = {};
    if( psDatumInfo == nullptr )
    {
        if( sTABProj.adDatumParams[0] == 0.0
            && sTABProj.adDatumParams[1] == 0.0
            && sTABProj.adDatumParams[2] == 0.0
            && sTABProj.adDatumParams[3] == 0.0
            && sTABProj.adDatumParams[4] == 0.0 )
        {
            snprintf( szDatumName, sizeof(szDatumName),
                     "MIF 999,%u,%.15g,%.15g,%.15g",
                     sTABProj.nEllipsoidId,
                     sTABProj.dDatumShiftX,
                     sTABProj.dDatumShiftY,
                     sTABProj.dDatumShiftZ );
        }
        else
        {
            snprintf( szDatumName, sizeof(szDatumName),
                     "MIF 9999,%u,%.15g,%.15g,%.15g,%.15g,%.15g,%.15g,%.15g,%.15g",
                     sTABProj.nEllipsoidId,
                     sTABProj.dDatumShiftX,
                     sTABProj.dDatumShiftY,
                     sTABProj.dDatumShiftZ,
                     sTABProj.adDatumParams[0],
                     sTABProj.adDatumParams[1],
                     sTABProj.adDatumParams[2],
                     sTABProj.adDatumParams[3],
                     sTABProj.adDatumParams[4] );
        }
    }
    else if( strlen(psDatumInfo->pszOGCDatumName) > 0 )
    {
        CPLStrlcpy( szDatumName, psDatumInfo->pszOGCDatumName,
                    sizeof(szDatumName) );
    }
    else
    {
        snprintf( szDatumName, sizeof(szDatumName), "MIF %d", psDatumInfo->nMapInfoDatumID );
    }

    /*-----------------------------------------------------------------
     * Set the spheroid.
     *----------------------------------------------------------------*/
    double dfSemiMajor = 0.0;
    double dfInvFlattening = 0.0;
    const char *pszSpheroidName = nullptr;

    for( int i = 0; asSpheroidInfoList[i].nMapInfoId != -1; i++ )
    {
        if( asSpheroidInfoList[i].nMapInfoId == sTABProj.nEllipsoidId )
        {
            dfSemiMajor = asSpheroidInfoList[i].dfA;
            dfInvFlattening = asSpheroidInfoList[i].dfInvFlattening;
            pszSpheroidName = asSpheroidInfoList[i].pszMapinfoName;
            break;
        }
    }

    // use WGS 84 if nothing is known.
    if( pszSpheroidName == nullptr )
    {
        pszSpheroidName = "unknown";
        dfSemiMajor = 6378137.0;
        dfInvFlattening = 298.257223563;
    }

    /*-----------------------------------------------------------------
     * Set the prime meridian.
     *----------------------------------------------------------------*/
    double      dfPMOffset = 0.0;
    const char *pszPMName = "Greenwich";

    if( /*sTABProj.nDatumId == 9999 ||*/ sTABProj.adDatumParams[4] != 0.0 )
    {
        dfPMOffset = sTABProj.adDatumParams[4];

        if( fabs(dfPMOffset - 2.337229166667) < 1e-10)
            pszPMName = "Paris";
        else
            pszPMName = "non-Greenwich";
    }

    /*-----------------------------------------------------------------
     * Create a GEOGCS definition.
     *----------------------------------------------------------------*/

    poSpatialRef->SetGeogCS( "unnamed",
                               szDatumName,
                               pszSpheroidName,
                               dfSemiMajor, dfInvFlattening,
                               pszPMName, dfPMOffset,
                               SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));

    if( psDatumInfo != nullptr )
    {
        if( CPLTestBool(CPLGetConfigOption("MITAB_SET_TOWGS84_ON_KNOWN_DATUM", "NO")) )
        {
            poSpatialRef->SetTOWGS84( psDatumInfo->dfShiftX,
                                        psDatumInfo->dfShiftY,
                                        psDatumInfo->dfShiftZ,
                                        psDatumInfo->dfDatumParm0 == 0 ? 0 : -psDatumInfo->dfDatumParm0, /* avoids 0 to be transformed into -0 */
                                        psDatumInfo->dfDatumParm1 == 0 ? 0 : -psDatumInfo->dfDatumParm1,
                                        psDatumInfo->dfDatumParm2 == 0 ? 0 : -psDatumInfo->dfDatumParm2,
                                        psDatumInfo->dfDatumParm3 );
        }
    }
    else
    {
        poSpatialRef->SetTOWGS84( sTABProj.dDatumShiftX,
                                    sTABProj.dDatumShiftY,
                                    sTABProj.dDatumShiftZ,
                                    sTABProj.adDatumParams[0] == 0 ? 0 : -sTABProj.adDatumParams[0],
                                    sTABProj.adDatumParams[1] == 0 ? 0 : -sTABProj.adDatumParams[1],
                                    sTABProj.adDatumParams[2] == 0 ? 0 : -sTABProj.adDatumParams[2],
                                    sTABProj.adDatumParams[3] );
    }

    /*-----------------------------------------------------------------
     * Special case for Google Mercator (datum=157, ellipse=54, gdal #4115)
     *----------------------------------------------------------------*/
    if( sTABProj.nProjId == 10
        && sTABProj.nDatumId == 157
        && sTABProj.nEllipsoidId == 54 )
    {
        poSpatialRef->SetNode( "PROJCS", "WGS 84 / Pseudo-Mercator" );
        poSpatialRef->SetExtension( "PROJCS", "PROJ4", "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs" );
    }

    /*-----------------------------------------------------------------
     * Special case for France Lambert-93
     *----------------------------------------------------------------*/
    if( sTABProj.nProjId == 3
        && sTABProj.nDatumId == 33
        && sTABProj.nEllipsoidId == 0
        && TAB_EQUAL(poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0), 3.0)
        && TAB_EQUAL(poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0), 46.5) )
    {
        poSpatialRef->SetNode( "PROJCS", "RGF93 / Lambert-93" );
        poSpatialRef->SetNode( "PROJCS|GEOGCS", "RGF93");
        poSpatialRef->SetNode( "PROJCS|GEOGCS|DATUM", "Reseau_Geodesique_Francais_1993");
    }

    if( sTABProj.nProjId == 3 )
    {
        // If the LCC_2SP can be turned into a LCC_1SP that has the same
        // latitude of origin, then it is a better candidate
        OGRSpatialReference* poLCC1SP =
            poSpatialRef->convertToOtherProjection(SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP);
        if( poLCC1SP )
        {
            if( TAB_EQUAL(poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0),
                          poLCC1SP->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0)) )
            {
                delete poSpatialRef;
                poSpatialRef = poLCC1SP;
            }
            else
            {
                delete poLCC1SP;
            }
        }
    }

    /* For LCC, standard parallel 1 and 2 can be switched indifferently */
    /* So the MapInfo order and the EPSG order are not generally identical */
    /* which may cause recognition problems when reading in MapInfo */
    if( sTABProj.nProjId == 3 )
    {
        double dfCenterLong = sTABProj.adProjParams[0];
        double dfCenterLat = sTABProj.adProjParams[1];
        double dfStdP1 = sTABProj.adProjParams[2];
        double dfStdP2 = sTABProj.adProjParams[3];

        for(size_t i=0;i<sizeof(asMapInfoLCCSRSList)/sizeof(asMapInfoLCCSRSList[0]);i++)
        {
            if( sTABProj.nDatumId == asMapInfoLCCSRSList[i].nMapInfoDatumID &&
                TAB_EQUAL( dfCenterLong, asMapInfoLCCSRSList[i].dfCenterLong ) &&
                TAB_EQUAL( dfCenterLat, asMapInfoLCCSRSList[i].dfCenterLat ) )
            {
                if( TAB_EQUAL( dfStdP1, asMapInfoLCCSRSList[i].dfStdP1 ) &&
                    TAB_EQUAL( dfStdP2, asMapInfoLCCSRSList[i].dfStdP2 ) )
                {
                    if( asMapInfoLCCSRSList[i].bReverseStdP )
                    {
                        CPLDebug("MITAB", "Switching standard parallel 1 and 2");
                        poSpatialRef->SetLCC( sTABProj.adProjParams[3],
                                sTABProj.adProjParams[2],
                                sTABProj.adProjParams[1],
                                sTABProj.adProjParams[0],
                                sTABProj.adProjParams[4],
                                sTABProj.adProjParams[5] );
                    }
                    if( asMapInfoLCCSRSList[i].nEPSGCode > 0 )
                        poSpatialRef->SetAuthority( "PROJCS", "EPSG",
                                    asMapInfoLCCSRSList[i].nEPSGCode );
                    break;
                }
            }
        }
    }

    return poSpatialRef;
}

/**********************************************************************
 *                   TABFile::SetSpatialRef()
 *
 * Set the OGRSpatialReference for this dataset.
 * A reference to the OGRSpatialReference will be kept, and it will also
 * be converted into a TABProjInfo to be stored in the .MAP header.
 *
 * Returns 0 on success, and -1 on error.
 **********************************************************************/
int TABFile::SetSpatialRef(OGRSpatialReference *poSpatialRef)
{
    if (m_eAccessMode != TABWrite)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "SetSpatialRef() can be used only with Write access.");
        return -1;
    }

    if (m_poMAPFile == nullptr )
    {
        CPLError(CE_Failure, CPLE_AssertionFailed,
                 "SetSpatialRef() failed: file has not been opened yet.");
        return -1;
    }

    if( poSpatialRef == nullptr )
    {
        CPLError(CE_Failure, CPLE_AssertionFailed,
                 "SetSpatialRef() failed: Called with NULL poSpatialRef.");
        return -1;
    }

    /*-----------------------------------------------------------------
     * Keep a copy of the OGRSpatialReference...
     * Note: we have to take the reference count into account...
     *----------------------------------------------------------------*/
    if (m_poSpatialRef && m_poSpatialRef->Dereference() == 0)
        delete m_poSpatialRef;

    m_poSpatialRef = poSpatialRef->Clone();

    TABProjInfo sTABProj;
    int nParamCount = 0;
    GetTABProjFromSpatialRef(poSpatialRef, sTABProj, nParamCount);

    /*-----------------------------------------------------------------
     * Set the new parameters in the .MAP header.
     * This will also trigger lookup of default bounds for the projection.
     *----------------------------------------------------------------*/
    if ( SetProjInfo( &sTABProj ) != 0 )
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "SetSpatialRef() failed setting projection parameters.");
        return -1;
    }

    return 0;
}

static int MITABGetCustomDatum(const OGRSpatialReference* poSpatialRef,
                               TABProjInfo& sTABProj)
{
    double  adfTOWGS[7] = {0};
    if(OGRERR_NONE != poSpatialRef->GetTOWGS84(adfTOWGS, sizeof(adfTOWGS)/sizeof(adfTOWGS[0])))
    {
        return FALSE;
    }
    sTABProj.nDatumId = 9999;
    sTABProj.dDatumShiftX = adfTOWGS[0];
    sTABProj.dDatumShiftY = adfTOWGS[1];
    sTABProj.dDatumShiftZ = adfTOWGS[2];
    sTABProj.adDatumParams[0] = -adfTOWGS[3];
    sTABProj.adDatumParams[1] = -adfTOWGS[4];
    sTABProj.adDatumParams[2] = -adfTOWGS[5];
    sTABProj.adDatumParams[3] = -adfTOWGS[6];

    int nSpheroidId = -1;

    const char *pszWKTSpheroid = poSpatialRef->GetAttrValue("SPHEROID");
    for( int i = 0; asSpheroidInfoList[i].nMapInfoId != -1; i++ )
    {
        if(EQUAL(pszWKTSpheroid, asSpheroidInfoList[i].pszMapinfoName))
        {
            nSpheroidId = asSpheroidInfoList[i].nMapInfoId;
            break;
        }
    }

    if(nSpheroidId == -1)
    {
        double adSemiMajor = poSpatialRef->GetSemiMajor();
        double adInvFlattening = poSpatialRef->GetInvFlattening();

        for( int i = 0; asSpheroidInfoList[i].nMapInfoId != -1; i++ )
        {
            if(CPLIsEqual(adSemiMajor, asSpheroidInfoList[i].dfA) &&
               CPLIsEqual(adInvFlattening, asSpheroidInfoList[i].dfInvFlattening))
            {
                nSpheroidId = asSpheroidInfoList[i].nMapInfoId;
                break;
            }
        }
    }
    if(nSpheroidId == -1)
    {
        CPLDebug("MITAB", "Cannot find MapInfo spheroid matching %s. Defaulting to WGS 84",
                 pszWKTSpheroid);
        nSpheroidId = 28; /* WGS 84 */
    }
    sTABProj.nEllipsoidId = static_cast<GByte>(nSpheroidId);
    return TRUE;
}

int TABFile::GetTABProjFromSpatialRef(const OGRSpatialReference* poSpatialRef,
                                      TABProjInfo& sTABProj, int& nParamCount)
{
    /*-----------------------------------------------------------------
     * Initialize TABProjInfo
     *----------------------------------------------------------------*/
    sTABProj.nProjId = 0;
    sTABProj.nEllipsoidId = 0; /* how will we set this? */
    sTABProj.nUnitsId = 7;
    sTABProj.adProjParams[0] = sTABProj.adProjParams[1] = 0.0;
    sTABProj.adProjParams[2] = sTABProj.adProjParams[3] = 0.0;
    sTABProj.adProjParams[4] = sTABProj.adProjParams[5] = 0.0;

    sTABProj.nDatumId = 0;
    sTABProj.dDatumShiftX = 0.0;
    sTABProj.dDatumShiftY = 0.0;
    sTABProj.dDatumShiftZ = 0.0;
    sTABProj.adDatumParams[0] = 0.0;
    sTABProj.adDatumParams[1] = 0.0;
    sTABProj.adDatumParams[2] = 0.0;
    sTABProj.adDatumParams[3] = 0.0;
    sTABProj.adDatumParams[4] = 0.0;

    sTABProj.nAffineFlag   = 0;
    sTABProj.nAffineUnits  = 7;
    sTABProj.dAffineParamA = 0.0;
    sTABProj.dAffineParamB = 0.0;
    sTABProj.dAffineParamC = 0.0;
    sTABProj.dAffineParamD = 0.0;
    sTABProj.dAffineParamE = 0.0;
    sTABProj.dAffineParamF = 0.0;

    /*-----------------------------------------------------------------
     * Get the linear units and conversion.
     *----------------------------------------------------------------*/
    const char *pszLinearUnits = nullptr;
    double dfLinearConv = poSpatialRef->GetLinearUnits( &pszLinearUnits );
    if( dfLinearConv == 0.0 )
        dfLinearConv = 1.0;

    // Get datum information
    const char *pszWKTDatum = poSpatialRef->GetAttrValue("DATUM");
    int nDatumEPSGCode = -1;
    const char *pszDatumAuthority = poSpatialRef->GetAuthorityName("DATUM");
    const char *pszDatumCode = poSpatialRef->GetAuthorityCode("DATUM");

    if (pszDatumCode && pszDatumAuthority && EQUAL(pszDatumAuthority, "EPSG"))
    {
        nDatumEPSGCode = atoi(pszDatumCode);
    }

    /*-----------------------------------------------------------------
     * Transform the projection and projection parameters.
     *----------------------------------------------------------------*/
    const char *pszProjection = poSpatialRef->GetAttrValue("PROJECTION");
    double      *params = sTABProj.adProjParams;
    nParamCount = 0;

    if( pszProjection == nullptr && poSpatialRef->GetAttrNode("GEOGCS") == nullptr)
    {
        /* nonearth */
        sTABProj.nProjId = 0;
    }

    else if( pszProjection == nullptr )
    {
        sTABProj.nProjId = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_ALBERS_CONIC_EQUAL_AREA) )
    {
        sTABProj.nProjId = 9;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,0.0);
        params[2] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        params[3] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 6;
    }

    else if( EQUAL(pszProjection,SRS_PT_AZIMUTHAL_EQUIDISTANT) )
    {
        sTABProj.nProjId = 5;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,0.0);
        params[2] = 90.0;
        nParamCount = 3;

        if( std::abs((std::abs(params[1]) - 90)) > 0.001 )
            sTABProj.nProjId = 28;
    }

    else if( EQUAL(pszProjection,SRS_PT_CYLINDRICAL_EQUAL_AREA) )
    {
        sTABProj.nProjId = 2;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        nParamCount = 2;
    }

    else if( EQUAL(pszProjection,SRS_PT_ECKERT_IV) )
    {
        sTABProj.nProjId = 14;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_ECKERT_VI) )
    {
        sTABProj.nProjId = 15;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_EQUIDISTANT_CONIC) )
    {
        sTABProj.nProjId = 6;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,0.0);
        params[2] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        params[3] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 6;
    }

    else if( EQUAL(pszProjection,SRS_PT_GALL_STEREOGRAPHIC) )
    {
        sTABProj.nProjId = 17;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_HOTINE_OBLIQUE_MERCATOR) )
    {
        sTABProj.nProjId = 7;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,0.0);
        params[2] = poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 6;
    }

    else if( EQUAL(pszProjection,SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA) )
    {
        sTABProj.nProjId = 4;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,0.0);
        params[2] = 90.0;
        nParamCount = 3;

        if( std::abs((std::abs(params[1]) - 90)) > 0.001 )
            sTABProj.nProjId = 29;
    }

    else if( EQUAL(pszProjection,SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) )
    {
        sTABProj.nProjId = 3;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        params[3] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 6;
    }

    else if( EQUAL(pszProjection,SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP) )
    {
        OGRSpatialReference* poOtherSRS =
            poSpatialRef->convertToOtherProjection(SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP);
        if( poOtherSRS )
        {
            sTABProj.nProjId = 3;
            params[0] = poOtherSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
            params[1] = poOtherSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
            params[2] = poOtherSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
            params[3] = poOtherSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
            params[4] = poOtherSRS->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
            params[5] = poOtherSRS->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
            nParamCount = 6;
            delete poOtherSRS;
        }
    }

    else if( EQUAL(pszProjection,SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM) )
    {
        sTABProj.nProjId = 19;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        params[3] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 6;
    }

    else if( EQUAL(pszProjection,SRS_PT_MERCATOR_1SP) )
    {
        sTABProj.nProjId = 10;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        nParamCount = 1; // FIXME for MIF export ?

        if( params[1] != 0.0 )
        {
            sTABProj.nProjId = 26;
            nParamCount = 2; // FIXME for MIF export ?
        }
    }

    else if( EQUAL(pszProjection,SRS_PT_MERCATOR_2SP) )
    {
        sTABProj.nProjId = 26;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0);
        nParamCount = 2; // FIXME for MIF export ?
    }

    else if( EQUAL(pszProjection,SRS_PT_MILLER_CYLINDRICAL) )
    {
        sTABProj.nProjId = 11;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_MOLLWEIDE) )
    {
        sTABProj.nProjId = 13;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_NEW_ZEALAND_MAP_GRID) )
    {
        sTABProj.nProjId = 18;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 4;
    }

    else if( EQUAL(pszProjection,SRS_PT_SWISS_OBLIQUE_CYLINDRICAL) )
    {
        sTABProj.nProjId = 25;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 4;
    }

    // Swiss Oblique expressed as Hotine Oblique Mercator Azimuth Center
    else if( EQUAL(pszProjection,SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER) &&
             std::abs( poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH, 90.0) - 90.0 ) < 1e-8 &&
             std::abs( poSpatialRef->GetNormProjParm(SRS_PP_RECTIFIED_GRID_ANGLE, 90.0) - 90.0 ) < 1e-8 &&
             std::abs( poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0) - 1.0 ) < 1e-8 )
    {
        sTABProj.nProjId = 25;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 4;
    }

    else if( EQUAL(pszProjection,SRS_PT_ROBINSON) )
    {
        sTABProj.nProjId = 12;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_SINUSOIDAL) )
    {
        sTABProj.nProjId = 16;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        nParamCount = 1;
    }

    else if( EQUAL(pszProjection,SRS_PT_STEREOGRAPHIC) )
    {
        sTABProj.nProjId = 20;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection,SRS_PT_OBLIQUE_STEREOGRAPHIC))
    {
        sTABProj.nProjId = 31;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if( EQUAL(pszProjection,SRS_PT_TRANSVERSE_MERCATOR) )
    {
        sTABProj.nProjId = 8;
        if( (pszWKTDatum && EQUAL(pszWKTDatum,
                "Kartastokoordinaattijarjestelma_1966")) || nDatumEPSGCode == 6123 )
        {
            // Special case for Finnish KKJ
            sTABProj.nProjId = 24;
        }
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 5;
    }

    else if( EQUAL(pszProjection,SRS_PT_TRANSVERSE_MERCATOR_MI_21) ) // Encom 2003
    {
        sTABProj.nProjId = 21;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 5;
    }

    else if( EQUAL(pszProjection,SRS_PT_TRANSVERSE_MERCATOR_MI_22) ) // Encom 2003
    {
        sTABProj.nProjId = 22;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 5;
    }

    else if( EQUAL(pszProjection,SRS_PT_TRANSVERSE_MERCATOR_MI_23) ) // Encom 2003
    {
        sTABProj.nProjId = 23;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 5;
    }

    else if( EQUAL(pszProjection,SRS_PT_TRANSVERSE_MERCATOR_MI_24) ) // Encom 2003
    {
        sTABProj.nProjId = 24;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR,1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 5;
    }

    else if( EQUAL(pszProjection,SRS_PT_CASSINI_SOLDNER) )
    {
        sTABProj.nProjId = 30;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 4;
    }

    else if( EQUAL(pszProjection,SRS_PT_POLYCONIC) )
    {
        sTABProj.nProjId = 27;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 4;
    }

   else if( EQUAL(pszProjection,SRS_PT_KROVAK) )
   {
        sTABProj.nProjId = 32;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetNormProjParm(SRS_PP_PSEUDO_STD_PARALLEL_1,0.0);
        params[3] = poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH,0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 6;
   }

  else if( EQUAL(pszProjection,SRS_PT_EQUIRECTANGULAR) )
  {
        sTABProj.nProjId = 33;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0);
        params[1] = poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING,0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING,0.0);
        nParamCount = 4;
  }

  else
  {
      CPLError(CE_Warning, CPLE_AppDefined,
               "No translation from %s to MapInfo known", pszProjection);
  }

    /* ==============================================================
     * Translate Datum and Ellipsoid
     * ============================================================== */
    const MapInfoDatumInfo *psDatumInfo = nullptr;

    /*-----------------------------------------------------------------
     * Default to WGS84 if we have no datum at all.
     *----------------------------------------------------------------*/
    if( pszWKTDatum == nullptr )
    {
        CPLDebug("MITAB", "Cannot find MapInfo datum matching %d. Defaulting to WGS 84",
                 nDatumEPSGCode);
        psDatumInfo = asDatumInfoList+0; /* WGS 84 */
        // From MIF export code. FIXME?
        //if( nProjection == 1 )
        //    nProjection = 0;
    }

    /*-----------------------------------------------------------------
     * We know the MIF datum number, and need to look it up to
     * translate into datum parameters.
     *----------------------------------------------------------------*/
    else if( STARTS_WITH_CI(pszWKTDatum, "MIF ")
             && atoi(pszWKTDatum+4) != 999
             && atoi(pszWKTDatum+4) != 9999 )
    {
        int nDatum = atoi(pszWKTDatum+4);
        for( int i = 0; asDatumInfoList[i].nMapInfoDatumID != -1; i++ )
        {
            if( nDatum == asDatumInfoList[i].nMapInfoDatumID )
            {
                psDatumInfo = asDatumInfoList + i;
                break;
            }
        }

        if( psDatumInfo == nullptr )
        {
            CPLDebug("MITAB", "Cannot find MapInfo datum matching %s. Defaulting to WGS 84",
                     pszWKTDatum);
            psDatumInfo = asDatumInfoList+0; /* WGS 84 */
        }
    }

    /*-----------------------------------------------------------------
     * We have the MIF datum parameters, and apply those directly.
     *----------------------------------------------------------------*/
    else if( STARTS_WITH_CI(pszWKTDatum, "MIF ")
             && (atoi(pszWKTDatum+4) == 999 || atoi(pszWKTDatum+4) == 9999) )
    {
        sTABProj.nDatumId = static_cast<GInt16>(atoi(pszWKTDatum+4));
        char **papszFields =
            CSLTokenizeStringComplex( pszWKTDatum+4, ",", FALSE, TRUE);

        if( CSLCount(papszFields) >= 5 )
        {
            sTABProj.nEllipsoidId = static_cast<GByte>(atoi(papszFields[1]));
            sTABProj.dDatumShiftX = CPLAtof(papszFields[2]);
            sTABProj.dDatumShiftY = CPLAtof(papszFields[3]);
            sTABProj.dDatumShiftZ = CPLAtof(papszFields[4]);
        }

        if( CSLCount(papszFields) >= 10 )
        {
            sTABProj.adDatumParams[0] = CPLAtof(papszFields[5]);
            sTABProj.adDatumParams[1] = CPLAtof(papszFields[6]);
            sTABProj.adDatumParams[2] = CPLAtof(papszFields[7]);
            sTABProj.adDatumParams[3] = CPLAtof(papszFields[8]);
            sTABProj.adDatumParams[4] = CPLAtof(papszFields[9]);
        }

        if( CSLCount(papszFields) < 5 )
        {
            CPLDebug("MITAB", "Cannot find MapInfo datum matching %s. Defaulting to WGS 84",
                     pszWKTDatum);
            psDatumInfo = asDatumInfoList+0; /* WGS 84 */
        }

        CSLDestroy( papszFields );
    }

    /*-----------------------------------------------------------------
     * We have a "real" datum name, and possibly an EPSG code for the
     * datum.  Try to look it up (using EPSG code first) and get the
     * parameters.  If we don't find it with either just use WGS84.
     *----------------------------------------------------------------*/
    else
    {
        for( int i = 0; asDatumInfoList[i].nMapInfoDatumID != -1; i++ )
        {
            if ( (nDatumEPSGCode > 0 && asDatumInfoList[i].nDatumEPSGCode == nDatumEPSGCode) ||
                 EQUAL(pszWKTDatum,asDatumInfoList[i].pszOGCDatumName) )
            {
                psDatumInfo = asDatumInfoList + i;
                break;
            }
        }

        if( psDatumInfo == nullptr &&
            !MITABGetCustomDatum(poSpatialRef, sTABProj))
        {
            CPLDebug("MITAB", "Cannot find MapInfo datum matching %s,%d. Defaulting to WGS 84",
                     pszWKTDatum, nDatumEPSGCode);
            psDatumInfo = asDatumInfoList+0; /* WGS 84 */
        }
    }

    if( psDatumInfo != nullptr )
    {
        sTABProj.nEllipsoidId = static_cast<GByte>(psDatumInfo->nEllipsoid);
        sTABProj.nDatumId = static_cast<GInt16>(psDatumInfo->nMapInfoDatumID);
        sTABProj.dDatumShiftX = psDatumInfo->dfShiftX;
        sTABProj.dDatumShiftY = psDatumInfo->dfShiftY;
        sTABProj.dDatumShiftZ = psDatumInfo->dfShiftZ;
        sTABProj.adDatumParams[0] = psDatumInfo->dfDatumParm0;
        sTABProj.adDatumParams[1] = psDatumInfo->dfDatumParm1;
        sTABProj.adDatumParams[2] = psDatumInfo->dfDatumParm2;
        sTABProj.adDatumParams[3] = psDatumInfo->dfDatumParm3;
        sTABProj.adDatumParams[4] = psDatumInfo->dfDatumParm4;

        /* For LCC, standard parallel 1 and 2 can be switched indifferently */
        /* So the MapInfo order and the EPSG order are not generally identical */
        /* which may cause recognition problems when reading in MapInfo */
        if( sTABProj.nProjId == 3 )
        {
            double dfCenterLong = params[0];
            double dfCenterLat = params[1];
            double dfStdP1 = params[2];
            double dfStdP2 = params[3];

            for(size_t i=0;i<sizeof(asMapInfoLCCSRSList)/sizeof(asMapInfoLCCSRSList[0]);i++)
            {
                if( sTABProj.nDatumId == asMapInfoLCCSRSList[i].nMapInfoDatumID &&
                    TAB_EQUAL( dfCenterLong, asMapInfoLCCSRSList[i].dfCenterLong ) &&
                    TAB_EQUAL( dfCenterLat, asMapInfoLCCSRSList[i].dfCenterLat ) )
                {
                    if( TAB_EQUAL( dfStdP1, asMapInfoLCCSRSList[i].dfStdP1 ) &&
                        TAB_EQUAL( dfStdP2, asMapInfoLCCSRSList[i].dfStdP2 ) )
                    {
                        break;
                    }
                    else if( TAB_EQUAL( dfStdP1, asMapInfoLCCSRSList[i].dfStdP2 ) &&
                                TAB_EQUAL( dfStdP2, asMapInfoLCCSRSList[i].dfStdP1 ) )
                    {
                        CPLDebug("MITAB", "Switching standard parallel 1 and 2");
                        double dfTmp = params[2];
                        params[2] = params[3];
                        params[3] = dfTmp;
                        break;
                    }
                }
            }
        }
    }

    // Google Merc
    const char* pszAuthorityName = nullptr;
    const char* pszAuthorityCode = nullptr;
    const char* pszExtension = nullptr;
    if( ((pszAuthorityName = poSpatialRef->GetAuthorityName(nullptr)) != nullptr &&
        EQUAL(pszAuthorityName, "EPSG") &&
        (pszAuthorityCode = poSpatialRef->GetAuthorityCode(nullptr)) != nullptr &&
        atoi(pszAuthorityCode) == 3857) ||
        ((pszExtension = poSpatialRef->GetExtension(nullptr, "PROJ4")) != nullptr &&
         (EQUAL(pszExtension,
               "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs") ||
          EQUAL(pszExtension,
               "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"))) )
    {
        sTABProj.nDatumId = 157;
        sTABProj.nEllipsoidId = 54;
    }

    /*-----------------------------------------------------------------
     * Translate the units
     *----------------------------------------------------------------*/
    if( sTABProj.nProjId == 1 || pszLinearUnits == nullptr )
        sTABProj.nUnitsId = 13;
    else if( dfLinearConv == 1000.0 )
        sTABProj.nUnitsId = 1;
    else if( dfLinearConv == 0.0254 || EQUAL(pszLinearUnits,"Inch")
             || EQUAL(pszLinearUnits,"IINCH") )
        sTABProj.nUnitsId = 2;
    else if( fabs(dfLinearConv - CPLAtof(SRS_UL_FOOT_CONV)) < 1e-15 * dfLinearConv
             || EQUAL(pszLinearUnits,SRS_UL_FOOT) )
        sTABProj.nUnitsId = 3;
    else if( EQUAL(pszLinearUnits,"YARD") || EQUAL(pszLinearUnits,"IYARD")
             || dfLinearConv == 0.9144 )
        sTABProj.nUnitsId = 4;
    else if( dfLinearConv == 0.001 )
        sTABProj.nUnitsId = 5;
    else if( dfLinearConv == 0.01 )
        sTABProj.nUnitsId = 6;
    else if( dfLinearConv == 1.0 )
        sTABProj.nUnitsId = 7;
    else if( fabs(dfLinearConv - CPLAtof(SRS_UL_US_FOOT_CONV)) < 1e-15 * dfLinearConv
             || EQUAL(pszLinearUnits,SRS_UL_US_FOOT) )
        sTABProj.nUnitsId = 8;
    else if( dfLinearConv == 1852.0 || EQUAL(pszLinearUnits,SRS_UL_NAUTICAL_MILE) )
        sTABProj.nUnitsId = 9;
    else if( EQUAL(pszLinearUnits,SRS_UL_LINK)
             || EQUAL(pszLinearUnits,"GUNTERLINK") )
        sTABProj.nUnitsId = 30;
    else if( EQUAL(pszLinearUnits,SRS_UL_CHAIN)
             || EQUAL(pszLinearUnits,"GUNTERCHAIN") )
        sTABProj.nUnitsId = 31;
    else if( EQUAL(pszLinearUnits,SRS_UL_ROD) )
        sTABProj.nUnitsId = 32;
    else if( EQUAL(pszLinearUnits,"Mile")
             || EQUAL(pszLinearUnits,"IMILE") )
        sTABProj.nUnitsId = 0;
    else
        sTABProj.nUnitsId = 7;

    return 0;
}
